package conf

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"
	"time"

	"gitee.com/micro-plat/sas/sas/modules/const/enum"
	"gitee.com/micro-plat/sas/sas/modules/util"

	"github.com/micro-plat/lib4go/errs"
	"github.com/micro-plat/lib4go/security/aes"
	"github.com/micro-plat/lib4go/security/des"
	"github.com/micro-plat/lib4go/security/padding"
	"github.com/micro-plat/lib4go/types"
)

//AuthConfig 认证配置
type AuthConfig struct {
	Connect        ConnectConf        `json:"connect" form:"connect"`               //原串连接配置
	Required       []string           `json:"required" form:"required"`             //必要参数，签名验证时会检查参数是否存在，可空
	Alias          map[string]string  `json:"alias" form:"alias" valid:"required"`  //参数别名，其中用于标记系统euid的别名必须配置
	Decrypt        []string           `json:"decrypt" form:"decrypt"`               //需要解密数据，可空
	CheckTimestamp bool               `json:"checkTimestamp" form:"checkTimestamp"` //是否校验时间戳
	Params         map[string]*Cipher `json:"params" form:"params"`                 //加密参数
}

type Cipher struct {
	Mode    string `json:"mode" form:"mode"`
	Padding string `json:"padding" form:"padding"`
}

//ConnectConf 原串连接配置
type ConnectConf struct {
	KV     string        `json:"kv" form:"kv"`         //签名原串中参数键与值连接符，可空
	Chain  string        `json:"chain" form:"chain"`   //签名原串中键值对之间连接符，可空
	Sort   string        `json:"sort" form:"sort"`     //签名原串排序方式，可空(默认data)
	Fields string        `json:"fields" form:"fields"` //签名原串字段排序顺序配置，仅static模式下使用，键名间用|隔开
	Secret SecretConnect `json:"secret" form:"secret"`
}

type SecretConnect struct {
	Name  string `json:"name" form:"name"`   //secret的键名称，可为空(如果密钥也要进行排序,默认为:secret)
	Mode  string `json:"mode" form:"mode"`   //secret连接方式。排序方式不是all时有效，值有head,tail, headTail
	KV    string `json:"kv" form:"kv"`       //secret与数据key,value的连接方式。未指定时使用connect.kv的连接方式
	Chain string `json:"chain" form:"chain"` //secret与数据的连接方式。未指定时使用connect.chain的连接方式
}

//GetParams 获取参数配置
func GetParams(data map[string]interface{}) (conf *AuthConfig, params map[string]interface{}, err error) {
	params = data
	//检查配置
	auth, ok := data["__auth_"]
	if !ok || auth == nil {
		err = errs.NewError(enum.ERR_MissingParam, fmt.Errorf("未收到签名配置:__auth_"))
		return
	}

	//配置转码
	confStr, err := url.QueryUnescape(types.GetString(auth))
	if err != nil {
		return
	}

	//配置解析
	conf, err = GetConf(confStr)
	if err != nil {
		return
	}

	//检查配置
	if err = conf.checkConnectConf(); err != nil {
		return
	}

	delete(params, "__auth_")
	return
}

//GetConf 获取配置
func GetConf(confStr string) (*AuthConfig, error) {
	conf := &AuthConfig{
		Params: map[string]*Cipher{
			"aes": {Mode: aes.AesCFB, Padding: "null"},
			"des": {Mode: des.DesCBC, Padding: padding.PaddingPkcs5},
			"rsa": {Mode: "md5"},
		},
	}
	if err := json.Unmarshal([]byte(confStr), conf); err != nil {
		return nil, fmt.Errorf("认证配置解析出错:%s: %v", confStr, err)
	}
	return conf, nil
}

// GetEncryptData 获取数据中的加密数据
func (a *AuthConfig) GetEncryptData(input map[string]interface{}) map[string]string {
	encrypt := make(map[string]string)
	for _, v := range a.Decrypt {
		data, ok := input[v]
		if ok && data != nil {
			encrypt[v] = data.(string)
		}
	}
	return encrypt
}

//Check 检查参数（必须参数，参数别名，时间戳）并获取uid
func (a *AuthConfig) Check(input map[string]interface{}, authType int) (euid, signName, encodeKey string, err error) {

	//获取必要参数
	req, euidName, tsName, signName, encodeKeyName := a.getRequiredParams(authType)
	//检查必须参数
	if err := requireValid(req, input); err != nil {
		return "", "", "", err
	}

	//检查时间戳
	if err := a.checkTimestamp(tsName, input); err != nil {
		return "", "", "", err
	}

	//获取uid
	euid = types.GetString(input[euidName])
	if authType == enum.Dynamic {
		encodeKey = types.GetString(input[encodeKeyName])
	}
	return
}

func (a *AuthConfig) getRequiredParams(authType int) (req []string, euidName, tsName, signName, encodeKey string) {
	filedMap := types.XMap{}
	for _, v := range a.Required {
		filedMap[v] = v
	}

	euidName = enum.EUIDName
	tsName = enum.TimestampName
	signName = enum.SignName
	encodeKey = enum.DynamicKeyName
	if authType == enum.Dynamic {
		filedMap.SetValue(encodeKey, encodeKey)
	}

	filedMap.Append(euidName, euidName, tsName, tsName, signName, signName)
	for k, v := range a.Alias {
		if v == "" {
			continue
		}
		if strings.EqualFold(k, euidName) {
			delete(filedMap, k)
			filedMap.SetValue(v, v)
			euidName = v
		}
		if strings.EqualFold(k, tsName) {
			delete(filedMap, k)
			filedMap.SetValue(v, v)
			tsName = v
		}
		if strings.EqualFold(k, signName) {
			delete(filedMap, k)
			filedMap.SetValue(v, v)
			signName = v
		}
		if strings.EqualFold(k, encodeKey) {
			if authType == enum.Dynamic {
				delete(filedMap, k)
				filedMap.SetValue(v, v)
				encodeKey = v
			}
		}
	}

	req = filedMap.Keys()
	return
}

//checkTimestamp 检查时间戳
func (a *AuthConfig) checkTimestamp(tsName string, input map[string]interface{}) error {
	if !a.CheckTimestamp {
		return nil
	}

	if !checkTimestamp(input[tsName].(string)) {
		return fmt.Errorf("时间戳不正确: %v", input[tsName])
	}

	return nil
}

//checkConnectConf 检查原串拼接配置
func (a *AuthConfig) checkConnectConf() error {

	a.Connect.Sort = strings.ToLower(a.Connect.Sort)
	a.Connect.Secret.Mode = strings.ToLower(a.Connect.Secret.Mode)

	//默认只对数据进行排序
	if a.Connect.Sort == "" {
		a.Connect.Sort = enum.Data
	}
	//密钥默认拼接在尾部
	if a.Connect.Secret.Mode == "" {
		a.Connect.Secret.Mode = enum.Tail
	}

	//如果密钥也要加入排序 设置密钥字段名,默认取"secret"
	if a.Connect.Sort == enum.All && a.Connect.Secret.Name == "" {
		a.Connect.Secret.Name = "secret"
	}

	//检查配置的排序类型及密钥拼接模式
	if err := checkSortType(a.Connect.Sort); err != nil {
		return err
	}
	if err := checkSecretMode(a.Connect.Secret.Mode); err != nil {
		return err
	}

	return nil
}

//GetSortFields 获取排序字段名数组
func (c *ConnectConf) GetSortFields() []string {
	return strings.Split(c.Fields, "|")
}

//GetSortFieldsLen 获取排序字段名数组长度
func (c *ConnectConf) GetSortFieldsLen() int {
	return len(strings.Split(c.Fields, "|"))
}

//requireValid 检查必需字段是否存在且不为空
func requireValid(keys []string, data map[string]interface{}) error {
	for _, key := range keys {
		if !util.CheckInMap(key, data) {
			return fmt.Errorf("%s值不能为空", key)
		}
	}
	return nil
}

//checkTimestamp 检查时间戳
func checkTimestamp(ts string) bool {
	now := time.Now().Format("20060102150405")
	if ts == now {
		return true
	}

	tsTime, err := time.Parse("20060102150405", ts)
	if err != nil {
		return false
	}
	nowTime, _ := time.Parse("20060102150405", now)
	//小于当前时间且大于3分钟前
	return tsTime.After(nowTime.Add(-3*time.Minute)) && tsTime.Before(nowTime)
}
